开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。
今日头条在消息服务平台和容灾体系建设方面的实践与思考
Angular 9即将发布:改进Ivy编译和渲染管道
被女朋友三番两次拉黑后,我用 Python 写了个“舔狗”必备神器
The following article is from CVPy Author 冰不语
神经元节点
层(layer)
权值(weights)
偏置项(bias)
每一层神经元数目(layer_neuron_num)
层(layer)
权值矩阵(weights)
偏置项(bias)
initNet():用来初始化神经网络
initWeights():初始化权值矩阵,调用initWeight()函数
initBias():初始化偏置项
forward():执行前向运算,包括线性运算和非线性激活,同时计算误差
backward():执行反向传播,调用updateWeights()函数更新权值。
Y = WX+b
来表示,其中X是输入样本,这里即是第N层的单列矩阵,W是权值矩阵,Y是加权求和之后的结果矩阵,大小与N+1层的单列矩阵相同。b是偏置,默认初始化全部为0。不难推知(鬼知道我推了多久!)
,W的大小是(N+1).rows * N.rows
。正如上一篇中生成weights矩阵的代码实现一样:O=f(Y)
来表示。Y就是上面得到的Y。O就是第N+1层的输出。f就是我们一直说的激活函数。激活函数一般都是非线性函数。它存在的价值就是给神经网络提供非线性建模能力。激活函数的种类有很多,比如sigmoid函数,tanh函数,ReLU函数等。各种函数的优缺点可以参考更为专业的论文和其他更为专业的资料。我们可以先来看一下前向函数forward()的代码:activationFunction()
里面实现了不同种类的激活函数,可以通过第二个参数来选取用哪一种。代码如下:Function.h
和Function.cpp
文件中。在此略去不表,感兴趣的请君移步Github
。需要再次提醒的是,上一篇博客中给出的Net类是精简过的,下面可能会出现一些上一篇Net类里没有出现过的成员变量。完整的Net类的定义还是在Github
里。反向传播过程第一个函数calcLoss()
计算输出误差和目标函数,所有输出误差平方和的均值作为需要最小化的目标函数。
第二个函数deltaError()
计算delta误差,也就是下图中delta1*df()那部分。
第三个函数updateWeights()
更新权值,也就是用下图中的公式更新权值。
calcLoss()
函数在Function.cpp
文件中:deltaError()
在Net.cpp
中:接受一个样本(即一个单列矩阵)作为输入,也即神经网络的第一层;
进行前向传播,也即forward()函数做的事情。然后计算loss;
如果loss值小于设定的阈值loss_threshold,则进行反向传播更新阈值;
重复以上过程直到loss小于等于设定的阈值。
用一组样本逐个输入神经网络;
通过前向传播得到一个输出值;
比较实际输出与理想输出,计算正确率。
predict()
函数和predict_one()
函数的区别相信很容易从名字看出来,那就是输入一个样本得到一个输出和输出一组样本得到一组输出的区别,显然predict()
应该是循环调用predict_one()
实现的。所以我们先看一下predict_one()
的代码:minMaxLoc()
函数来寻找矩阵中最大值的位置。float
类型保存的,这种数值的矩阵Mat不能直接保存为图片格式,所以这里我选择了把预处理之后的样本矩阵和标签矩阵保存到xml文档中。在源码中可以找到把原始的csv文件转换成xml文件的代码。在csv2xml.cpp
中。而我转换完成的MNIST的部分数据保存在data文件夹中,可以在Github上找到。在opencv中xml的读写非常方便,如下代码是写入数据:get_input_label()
从xml文件中从指定的列开始提取一定数目的样本和标签。默认从第0列开始读取,只是上面函数的简单封装:layer_neuron_num
,各层神经元数目,这是生成神经网络需要的唯一参数。
weights
,神经网络初始化之后需要用训练好的权值矩阵去初始化权值。
activation_function
,使用神经网络的过程其实就是前向计算的过程,显然需要知道激活函数是什么。
learning_rate
,如果要在现有模型的基础上继续训练以得到更好的模型,更新权值的时候需要用到这个函数。
xml
格式,上一篇已经提到了保存和加载xml
是多么的方便:这里只是重复一下,这一部分的代码在既然说到了输出的组织方式,那就顺便也提一句输入的组织方式。生成神经网络的时候,每一层都是用一个单列矩阵来表示的。显然第一层输入层就是一个单列矩阵。所以在对数据进行预处理的过程中,我就是把输入样本和标签一列一列地排列起来,作为矩阵存储。标签矩阵的第一列即是第一列样本的标签。以此类推。 把输出层设置为一个单列十行的矩阵,标签是几就把第几行的元素设置为1,其余都设为0。由于编程中一般都是从0开始作为第一位的,所以位置与0-9的数字正好一一对应。我们到时候只需要找到输出最大值所在的位置,也就知道了输出是几。”
csv2xml.cpp
中:Mat digit
的作用是,检验下转换后的矩阵和标签是否对应正确这里是把col(3),也就是第四个样本从一行重新变成28x28的图像,看上面的第一张图的第一列可以看到,第四个样本的标签是4。那么它转换回来的图像时什么样呢?是下面这样:给定每层的神经元数目,初始化神经网络和权值矩阵
从inputlabel1000.xml文件中取前800个样本作为训练样本,后200作为测试样本。
这是神经网络的一些参数:训练时候的终止条件,学习率,激活函数类型
前800样本训练神经网络,直到满足loss小于阈值loss_threshold,停止。
后200样本测试神经网络,输出正确率。
保存训练得到的模型。
开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。